// 导入Node.js内置的加密模块，用于验证请求签名
// crypto模块提供了加密功能，包括哈希、HMAC、加密、解密等操作
import crypto from "node:crypto"

// 导入Vercel的请求和响应类型定义，用于TypeScript类型检查
// VercelRequest: 表示HTTP请求对象，包含查询字符串、参数、头信息等
// VercelResponse: 表示HTTP响应对象，用于发送响应数据
import type { VercelRequest, VercelResponse } from "@vercel/node"
// 导入raw-body库，用于获取HTTP请求的原始数据体
// getRawBody: 函数用于获取HTTP请求的原始未解析数据体
import getRawBody from "raw-body"

/**
 * 默认的请求处理函数
 * 这是Vercel webhook的主要入口点，处理来自Vercel的事件通知
 * 使用export default关键字导出，表示这是该模块的默认导出
 * async关键字表示这是一个异步函数，可以使用await关键字
 * @param request - HTTP请求对象，包含来自Vercel的数据
 *        request.headers: 包含HTTP请求头信息
 *        request.body: 请求体数据
 * @param response - HTTP响应对象，用于向Vercel返回处理结果
 *        response.status(): 设置HTTP状态码的方法
 *        response.json(): 发送JSON格式响应的方法
 *        response.end(): 结束响应并发送数据的方法
 */
export default async function handler(request: VercelRequest, response: VercelResponse) {
  // 从环境变量中获取webhook密钥，用于验证请求来源的合法性
  // process.env: Node.js全局对象，用于访问环境变量
  // 解构赋值语法，将环境变量WEBHOOK_SECRET重命名为INTEGRATION_SECRET
  const { WEBHOOK_SECRET: INTEGRATION_SECRET } = process.env

  // 检查是否配置了密钥，如果没有则返回错误
  // typeof: JavaScript操作符，用于检测变量的数据类型
  // !==: 严格不等于操作符，同时比较值和类型
  if (typeof INTEGRATION_SECRET != "string") {
    // response.status(400): 设置HTTP状态码为400(Bad Request)
    // response.json(): 发送JSON格式的错误信息
    return response.status(400).json({
      code: "invalid_secret",
      error: "No integration secret found",
    })
  }

  // 获取请求的原始数据体（不经过解析的原始字节数据）
  // await: 等待Promise解析完成，只能在async函数中使用
  // getRawBody(): 获取原始请求体数据，返回Promise<Buffer>
  const rawBody = await getRawBody(request)

  // 使用密钥对原始数据体进行SHA1哈希计算，生成签名
  // sha1(): 自定义函数，用于生成数据签名
  const bodySignature = sha1(rawBody, INTEGRATION_SECRET)

  // 将我们计算的签名与请求头中的签名进行比较
  // request.headers: 包含所有HTTP请求头的对象
  // !==: 严格不等于操作符，防止哈希长度扩展攻击
  if (bodySignature !== request.headers["x-vercel-signature"]) {
    // response.status(403): 设置HTTP状态码为403(Forbidden)
    return response.status(403).json({
      code: "invalid_signature",
      error: "signature didn't match",
    })
  }

  // 将原始数据体解析为JSON对象，以便后续处理
  // JSON.parse(): 将JSON字符串解析为JavaScript对象
  // rawBody.toString("utf-8"): 将Buffer转换为UTF-8编码的字符串
  const json = JSON.parse(rawBody.toString("utf-8"))

  // 根据事件类型进行不同的处理
  // switch: JavaScript控制流语句，根据不同的值执行不同的代码块
  switch (json.type) {
    // 处理部署成功的事件
    // https://vercel.com/docs/observability/webhooks-overview/webhooks-api#deployment.succeeded
    case "deployment.succeeded": {
      // 从事件数据中获取部署目标（production表示生产环境）
      // 解构赋值语法，从json.payload或json.data中提取target属性
      const { target } = json.payload || json.data

      // 只有当部署到生产环境时才执行缓存清除操作
      // ===: 严格相等操作符，同时比较值和类型
      if (target === "production") {
        // await: 等待异步函数执行完成
        // purgeCloudflareCache(): 自定义异步函数，用于清除Cloudflare缓存
        await purgeCloudflareCache()
      } else {
        // 对于非生产环境的部署，只记录日志不执行操作
        // console.info(): 在控制台输出信息日志
        console.info(`Skipping non-production deployment: ${target}`, json)
      }

      // 跳出switch语句，防止继续执行其他case
      break
    }

    // 其他未处理的事件类型可以在这里添加
    // ...
  }

  // 返回成功响应给Vercel，表示我们已成功处理了webhook
  // response.status(200): 设置HTTP状态码为200(OK)
  // response.end(): 结束响应并发送文本数据
  return response.status(200).end("OK")
}

/**
 * 使用SHA1算法和密钥对数据进行签名
 * 这是验证webhook请求来源的标准方法
 * function: JavaScript关键字，用于声明函数
 * @param data - 需要签名的数据（Buffer格式）
 *        Buffer: Node.js中的二进制数据类型
 * @param secret - 用于签名的密钥
 * @returns 签名结果（十六进制字符串）
 *          string: JavaScript字符串类型
 */
function sha1(data: Buffer, secret: string): string {
  // crypto.createHmac(): 创建HMAC对象，用于生成基于密钥的哈希消息认证码
  // "sha1": 指定使用的哈希算法为SHA1
  // .update(data): 更新HMAC对象的数据
  // .digest("hex"): 计算摘要并以十六进制字符串形式返回
  return crypto.createHmac("sha1", secret).update(data).digest("hex")
}

// Vercel配置对象，用于自定义API行为
// export: ES6模块系统关键字，用于导出变量、函数或对象
export const config = {
  api: {
    // 禁用bodyParser，因为我们需要自己处理原始请求体
    // 这对于验证webhook签名是必需的
    // bodyParser: Vercel内置中间件，用于解析请求体
    bodyParser: false,
  },
}

/**
 * 清除Cloudflare CDN缓存
 * 当网站内容更新后，需要清除CDN缓存以确保用户访问到最新内容
 * async function: 声明异步函数，可以使用await关键字
 */
async function purgeCloudflareCache() {
  // 从环境变量中获取Cloudflare的API令牌和区域ID
  // 解构赋值语法，同时获取多个环境变量
  const { CF_TOKEN, CF_ZONE_ID } = process.env

  // 检查必要的环境变量是否存在
  // ||: 逻辑或操作符，任一条件为真则表达式为真
  if (typeof CF_TOKEN !== "string" || typeof CF_ZONE_ID !== "string") {
    // throw: JavaScript关键字，用于抛出异常
    // new TypeError(): 创建类型错误对象
    throw new TypeError("No Cloudflare token or zone ID found")
  }

  // 构建Cloudflare API的URL
  // 模板字符串语法，使用反引号(`)包围，${}插入变量
  const apiUrl = `https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/purge_cache`

  // 获取需要清除缓存的文件清单
  // await fetch(): 等待HTTP请求完成
  // fetch(): 现代浏览器和Node.js内置的HTTP客户端API
  // .then(): Promise链式调用，处理解析后的结果
  // res.text(): 将响应体解析为文本格式
  const manifestPath = await fetch(`https://app.folo.is/assets/manifest.txt?t=${Date.now()}`).then(
    (res) => res.text(),
  )

  try {
    // 首先尝试通过标签清除缓存（更高效的方法）
    // await fetch(): 发起HTTP请求并等待结果
    await fetch(apiUrl, {
      method: "POST", // HTTP请求方法
      headers: {
        // 使用Authorization头部传递API令牌
        // HTTP请求头对象
        Authorization: CF_TOKEN,
      },
      body: JSON.stringify({
        // JSON.stringify(): 将JavaScript对象转换为JSON字符串
        // 指定要清除的缓存标签
        tags: ["follow-assets"],
      }),
    })

    console.info("Successfully purged Cloudflare cache")
  } catch {
    // catch: try-catch语句的一部分，捕获并处理异常
    // 如果通过标签清除失败，则回退到逐个文件清除的方式
    console.error("Failed to purge Cloudflare cache by tags, fallback to purge by files")

    // 将清单中的路径转换为完整的URL
    // .split(): 字符串方法，按指定分隔符分割字符串为数组
    // .map(): 数组方法，对每个元素执行回调函数并返回新数组
    // .at(0): 数组方法，返回数组中指定位置的元素（0表示第一个）
    // ?? : 空值合并操作符，当前面的值为null或undefined时返回后面的值
    const allPath = manifestPath.split("\n").map((path) => `https://app.folo.is/${path}`)

    // 定义延迟函数，用于控制请求频率
    // Promise: JavaScript异步编程核心对象
    // setTimeout: 浏览器和Node.js内置函数，延迟执行代码
    const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

    // 存储所有清除请求的Promise数组
    // Array: JavaScript数组构造函数
    // as: TypeScript类型断言，指定变量类型
    const taskPromise = [] as Promise<Response>[]

    // 批量处理文件清除请求（每次处理30个文件）
    // for: JavaScript循环语句
    for (let i = 0; i < allPath.length; i += 30) {
      // 获取当前批次的文件路径
      // .slice(): 数组方法，提取数组的一部分并返回新数组
      const batch = allPath.slice(i, i + 30)

      // 发起清除请求
      // fetch(): 发起HTTP请求
      const r = fetch(apiUrl, {
        method: "POST",
        headers: {
          Authorization: CF_TOKEN,
        },
        body: JSON.stringify({
          // 指定当前批次要清除的文件
          files: batch,
        }),
      })

      // 将请求Promise添加到数组中
      // .push(): 数组方法，向数组末尾添加元素
      taskPromise.push(r)

      // 在批次之间添加延迟，避免请求过于频繁
      // < : 小于比较操作符
      if (i + 30 < allPath.length) {
        // await: 等待异步函数执行完成
        await delay(500)
      }
    }

    // 等待所有清除请求完成
    // Promise.allSettled(): 等待所有Promise完成（无论成功或失败）
    const result = await Promise.allSettled(taskPromise)

    // 输出处理结果统计
    // .filter(): 数组方法，过滤满足条件的元素
    // .length: 数组属性，返回数组元素个数
    console.info(`Success: ${result.filter((r) => r.status === "fulfilled").length}`)
    console.info(`Failed: ${result.filter((r) => r.status === "rejected").length}`)
  }
}
